﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AmiEmuLib.AmigaMachine.AmigaMemory;
using AmiEmuLib.AmigaMachine;
using System.Xml;
using System.Diagnostics;
using System.Collections;

namespace AmiEmuLib.SaveState
{
    /// <summary>
    /// Machine memory state.
    /// </summary>
    [DebuggerDisplay("Banks = {Count}, Prev = {Prev != null}")]
    public class MemoryState
    {
        /// <summary>
        /// Memory banks.
        /// </summary>
        private List<MemoryBankState> m_banks = new List<MemoryBankState>();

        /// <summary>
        /// Previous memory state.
        /// </summary>
        public readonly MemoryState Prev;

        /// <summary>
        /// Constructor.
        /// </summary>
        internal MemoryState()
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_prev"></param>
        internal MemoryState(MemoryState a_prev)
        {
            Prev = a_prev;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_memory"></param>
        /// <param name="a_prev"></param>
        internal MemoryState(Memory a_memory, MemoryState a_prev = null)
        {
            Prev = a_prev;

            if (a_prev == null)
            {
                foreach (var bank in a_memory.GetValidBanks())
                    m_banks.Add(new MemoryBankState(bank));
            }
            else
            {
                for (int i=0; i < Prev.Banks.Count(); i++)
                {
                    MemoryBank[] vmbs = a_memory.GetValidBanks().ToArray();
                    var bank_state = new MemoryBankState(vmbs[i], Prev[i]);
                    if (bank_state.Ranges.Count() != 0)
                        m_banks.Add(bank_state);
                }
            }
        }

        /// <summary>
        /// Return cell value. Before state will be persisted returns machine memory cell value.
        /// </summary>
        /// <param name="a_address"></param>
        /// <returns></returns>
        public ushort GetWord(uint a_address)
        {
            foreach (var mbs in m_banks)
            {
                if (mbs.InRange(a_address))
                    return mbs.GetWord(a_address);
            }

            return Prev.GetWord(a_address);
        }

        /// <summary>
        /// Read xml.
        /// </summary>
        /// <param name="a_reader"></param>
        internal void ReadXml(XmlReader a_reader)
        {
            a_reader.ReadStartElement(GetType().Name);

            while (a_reader.IsStartElement())
                m_banks.Add(new MemoryBankState(a_reader));

            a_reader.ReadEndElement();
        }

        /// <summary>
        /// Write xml.
        /// </summary>
        /// <param name="a_writer"></param>
        internal void WriteXml(XmlWriter a_writer)
        {
            foreach (var bank in m_banks)
            {
                a_writer.WriteStartElement(bank.GetType().Name);
                bank.WriteXml(a_writer);
                a_writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Restore state into machine memory.
        /// </summary>
        /// <param name="a_memory"></param>
        internal void Restore(Memory a_memory)
        {
            if (Prev != null)
                Prev.Restore(a_memory);

            foreach (var mbs in m_banks)
                mbs.Restore(a_memory);
        }

        /// <summary>
        /// Do not use directly. Use State.Equal(). Prev states are not compare.
        /// </summary>
        /// <param name="a_obj"></param>
        /// <returns></returns>
        public override bool Equals(object a_obj)
        {
            if (a_obj == null)
                return false;
            MemoryState state = a_obj as MemoryState;
            if (state == null)
                return false;

            return m_banks.SequenceEqual(state.m_banks);
        }

        public override int GetHashCode()
        {
            int hash = 0;

            foreach (var bank_state in m_banks)
                hash ^= bank_state.GetHashCode();

            return hash;
        }

        /// <summary>
        /// Memory banks.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<MemoryBankState> Banks
        {
            get
            {
                return m_banks;
            }
        }

        /// <summary>
        /// Get bank state at index.
        /// </summary>
        /// <param name="a_index"></param>
        /// <returns></returns>
        public MemoryBankState this[int a_index]
        {
            get
            {
                return m_banks[a_index];
            }
        }

        /// <summary>
        /// Nothing changed from previous state.
        /// </summary>
        public bool NothingChanged
        {
            get
            {
                return m_banks.Count() == 0;
            }
        }
    }
}
